#include <QtGui>
#include <GL/glew.h>
#include <QGLWidget>
#include <GL/glut.h>

#include "Vector/vector.03.h"
#include "Timer/timer.01.h"
#include "Color/color.01.h"

const double pi = 3.141592653589793;

//#if defined(__APPLE__) || defined(__MACOSX)

#define SQR(x) ((x)*(x))
#define GL_POINT_SPRITE_ARB               0x8861
#define GL_COORD_REPLACE_ARB              0x8862
#define GL_VERTEX_PROGRAM_POINT_SIZE_NV   0x8642

#define STRINGIFY(A) #A



// vertex shader
const char *vertexShaderCode = STRINGIFY (
	
	uniform float pointRadius;  // point size in world space
	uniform float pointScale;
	uniform int perspectiveF;

	void main() {
		
		 // calculate window-space point size
		vec3 posEye = vec3(gl_ModelViewMatrix * vec4(gl_Vertex.xyz, 1.0));
		float dist = length(posEye);
		
		if (perspectiveF==1) 
			gl_PointSize = pointRadius*pointScale / -posEye.z;
		else 
			gl_PointSize = pointRadius;
		
		gl_TexCoord[0] = gl_MultiTexCoord0;
		gl_Position = gl_ModelViewProjectionMatrix * vec4(gl_Vertex.xyz, 1.0);
		gl_FrontColor = gl_Color;
		
	}
);


const char *spherePixelShaderCode = STRINGIFY(

	void main() {
		
		const vec3 lightDir = vec3(0.577, 0.577, 0.577);

		// calculate normal from texture coordinates
		vec3 N;
		N.xy = gl_TexCoord[0].xy*vec2(2.0, -2.0) + vec2(-1.0, 1.0);
		float mag = dot(N.xy, N.xy);

		if (mag > 1.0) discard;   // kill pixels outside circle

		N.z = sqrt(1.0-mag);

		// calculate lighting
		float diffuse = max(0.0, dot(lightDir, N));

		gl_FragColor = gl_Color * diffuse;
		
	}
);




class Visualizator: public QGLWidget {

	public:
		
		class Eye {
			public:	
		
				typedef float T;
		
				Eye(T Rx, T Ry, T Rz, T Cx, T Cy, T Cz, T Ux, T Uy, T Uz):
				R(Rx,Ry,Rz), RM(R),
				U(Ux,Uy,Uz), UM(U),
				C(Cx,Cy,Cz), CM(C),
				PX(1,0,0), PY(0,1,0), PZ(0,0,1)
				{}
			
				fVector R,RM, U,UM,  C,CM; // M - memory
				fVector PX, PY, PZ;
		
				void rotateXY(T x, T y, bool apply){
	
					R=RM;	U=UM;
	
					T a1 = 0.5*pi-atan2(y,x); // angle btw U and XY
					T a2 = pi*sqrt(x*x+y*y);  // mouse shift on screen height (on 1) leads to rotion on pi 

					fVector RC = R-C;
					fVector XY = ROTATE(U, U%RC, a1);

					R = ROTATE(RC, XY, -a2)+C; // camera and object view motion are oposit 
					fVector XY_new = ROTATE(XY, RC, a2); // folow by the RV vector
	
					U = ORT(ROTATE(XY_new, ORT(XY%RC), -a1));
	
					if (apply) { RM = R; UM = U;}
	
				}	
		
				void rotateZ(T a, bool apply) {
	
					U = ROTATE(UM, UM%(R-C), a);

					if (apply) UM=U;
		
				}
		
				void moveZ(T z){

					RM=R = z*ORT(R-C)+C;

				}
		
			private:
		
				fVector ROTATE(fVector V1, fVector V2, T a){
					return module(V1)*((T)cos(a)*ORT(V1)+ (T)sin(a)*ORT(V2));	
				}

				fVector ORT(fVector V){ 
					return V.ort(); 
				}

				Eye::T module(fVector V){ 
					return V.module(); 
				}
		
		} eye;
		
		float scale;
		float scale_steps;
		float timeStep;
		float timeFlag;
		
		float pointSize;
		
		bool perspectiveF;
		bool gridF;
		bool pivotF;
		bool infoF;
		bool depthF;
		bool centerF;
		
		Timer wallTime;
		int glTimerID, fpsTimerID;
		
		int frames;
		double framesTime;
		double fps;
		float fov;

		float grid_step;
		int 	grid_size;
		Color grid_color;
		
		float centerx;
		float centery;
		
		unsigned int texture;
		unsigned int program;
		
		unsigned int vertexShader;
		unsigned int pixelShader;
		
		
		
		
		Visualizator(QWidget *parent=0): QGLWidget(parent),
		eye(0,-1,0, 0,0,0, 0,0,1),
		mouseEvent0(0), 
		scale(0), scale_steps(32),
		pointSize(1),
		fov(2*atan(.5)*180.0/M_PI),
		perspectiveF(true),
		gridF(true),
		pivotF(true),
		infoF(true),
		depthF(false),
		centerF(false),
		timeStep(17), // [ms]
		frames(0),
		framesTime(0),
		fps(0),
		grid_step(0.1),
		grid_size(10),
		grid_color(0.15,0.15,0.15,1),
		centerx(0.5), centery(0)
		{ 
			setAutoFillBackground(false);
			wallTime.set();
			wallTime.sin = 0.001*timeStep;
			glTimerID = startTimer(timeStep);
			fpsTimerID = startTimer(250); // fps info time
			timeFlag = true;
		};
		
		
		void timerEvent(QTimerEvent *event) {
			
			if (event->timerId() == glTimerID) {
				
				wallTime.set();
				if (timeFlag) moveTime(wallTime.sin+wallTime.sout);
				updateGL();
				
				framesTime+=wallTime.sin+wallTime.sout;
				frames++;
				wallTime.get();
				
			}
			
			if (event->timerId() == fpsTimerID){
				fps = frames/framesTime;
				framesTime=frames=0;
			}
			
			
		}
		
		virtual void moveTime(double dt){

		}
		
		void ROTATE(dVector &R, dVector w){
			
			if ((R%w).module() == 0) return;
			
			dVector wz = w.ort();
			dVector wy = (w%R).ort();
			dVector wx = (wz%wy).ort();
							
			dVector rz = (R*wz)*wz;
			dVector rx = (R*wx)*wx;
							
			rx = rx.rotate(wy, w.module());
				
			R = rz+rx;

		}
		
		
		virtual void initializeGL() {  // Set up the rendering context, define display lists etc.:

			glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
			glShadeModel(GL_FLAT);
			glClearDepth(1.0);							// Enables Clearing Of The Depth Buffer
			glEnable(GL_DEPTH_TEST);					// Enables Depth Testing
			

			glewInit();
			
			vertexShader = glCreateShader(GL_VERTEX_SHADER);
			pixelShader = glCreateShader(GL_FRAGMENT_SHADER);
			
			const char* v = vertexShaderCode;
			const char* p = spherePixelShaderCode;
			
			glShaderSource(vertexShader, 1, &v, 0);
			glShaderSource(pixelShader, 1, &p, 0);
			
			glCompileShader(vertexShader);
			glCompileShader(pixelShader);
			
			program = glCreateProgram();
			
			glAttachShader(program, vertexShader);
			glAttachShader(program, pixelShader);

			glLinkProgram(program);
			

			
			
		}

		void resizeGL(int w, int h){  // setup viewport, projection etc.:
			glViewport(0, 0, w, h);
		}
		
		virtual void paintGL() {
			
			
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			

			glMatrixMode ( GL_PROJECTION );
			glLoadIdentity(); // загружает еденичную матрицу
			
			float aspect = (float)width()/height();
			
			perspectiveF ? gluPerspective(fov, aspect, 0.001, 100) 
							 : centerF ? glOrtho(-aspect*.5, +aspect*.5, -0.5, 0.5, -1000, 1000) :
											 glOrtho(centery-aspect*.5, centery+aspect*.5, centerx-0.5, centerx+0.5, -1000, 1000);
											 
											 
			
			
			glMatrixMode(GL_MODELVIEW); 
			glLoadIdentity();
			gluLookAt(	eye.R.x, eye.R.y, eye.R.z, 
							eye.C.x, eye.C.y, eye.C.z, 
							eye.U.x, eye.U.y, eye.U.z );
			
			float _scale = pow(10, scale);
			glScalef(_scale, _scale, _scale);
	
			
			if (pivotF) drawPivot();
			if (gridF) drawGrid();
			if (infoF) printInfo();

		}

		void bufferArray(int bufID, float *data, uint size){
			
			glBindBuffer(GL_ARRAY_BUFFER_ARB, bufID);
			glBufferData(GL_ARRAY_BUFFER_ARB, size, data, GL_STATIC_DRAW_ARB);
			
		};
		
		void drawPointArray(int bufID, uint N, float *color, 
								  float *secondaryColor, float pointSize, 
							 GLenum sfactor, GLenum dfactor, bool depthF, 
							 uint program, uint texture, 
							 GLsizei stride, const GLvoid* ptr, GLenum type ) {
			
			glColor3fv(color);
			
			glEnable(GL_POINT_SPRITE_ARB);
			glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
			glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_NV);
			glDepthMask(GL_TRUE);
			glEnable(GL_DEPTH_TEST);
			
			glUseProgram(program);

			printf("%g\n", height() / tanf(fov*0.5f*(float)M_PI/180.0f) );
			
			glUniform1f(glGetUniformLocation(program, "pointScale"), 100);
			glUniform1f(glGetUniformLocation(program, "pointRadius"), pow(10, scale)*pointSize*height());
			
			
			
			
			

			glBindBufferARB(GL_ARRAY_BUFFER_ARB, bufID);
			glVertexPointer(3, type, stride, ptr);
			glEnableClientState(GL_VERTEX_ARRAY);		
			
			/*
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, 2);
			glColorPointer(4, GL_FLOAT, 0, 0);
			glEnableClientState(GL_COLOR_ARRAY);
			*/
			
			glDrawArrays(GL_POINTS, 0, N);
			
			
			//glDisableClientState(GL_COLOR_ARRAY);
			glDisableClientState(GL_VERTEX_ARRAY); 
			
			glUseProgram(0);
			glDisable(GL_POINT_SPRITE_ARB);
			
		};


		virtual void printInfo(){
			
			char str[512];
			sprintf(str, "fps: %.1f\nscale: %.4g\npointSize: %.4g\nfov: %g\n", fps, pow(10, scale), pointSize,  perspectiveF ? fov : 0);
			glColor3f (.4,.4,.4);
			glPrint(10,20, str, GLUT_BITMAP_HELVETICA_12);
			
		}
		
		
		void drawPivot(){
			
			// PIVOT
			glPointSize(1);
			glBegin(GL_LINES);
			glColor3f (.5,0,0);
			glVertex3f(0,0,0);
			glVertex3f(1,0,0);
			
			glColor3f (0,.5,0);
			glVertex3f(0,0,0);
			glVertex3f(0,1,0);
   
			glColor3f (0,0,.5);
			glVertex3f(0,0,0);
			glVertex3f(0,0,1);
			glEnd();
			
			// draw axis lables
			glColor3f(0.7,0.4,0.4);
			glPrint(fVector(1.03,0,0), "x", GLUT_BITMAP_HELVETICA_12);
			glColor3f(0.4,0.7,0.4);
			glPrint(fVector(0,1.03,0), "y", GLUT_BITMAP_HELVETICA_12);
			glColor3f(0.4,0.4,.7);
			glPrint(fVector(0,0,1.03), "z", GLUT_BITMAP_HELVETICA_12);
			
		}
		
		virtual void drawGrid(){
			
			glColor4fv(grid_color);
			glBegin(GL_LINES);
			for (int i=-grid_size; i<=grid_size; i++) {
				
				glVertex3f(grid_step*i,-grid_step*grid_size,0);
				glVertex3f(grid_step*i,+grid_step*grid_size,0);
				glVertex3f(-grid_step*grid_size,grid_step*i,0);
				glVertex3f(+grid_step*grid_size,grid_step*i,0);

				
				glVertex3f(0,-grid_step*grid_size,grid_step*i);
				glVertex3f(0,+grid_step*grid_size,grid_step*i);
				glVertex3f(-grid_step*grid_size,0,grid_step*i);
				glVertex3f(+grid_step*grid_size,0,grid_step*i);
				
				glVertex3f(grid_step*i,0,-grid_step*grid_size);
				glVertex3f(grid_step*i,0,+grid_step*grid_size);
				glVertex3f(0,grid_step*i,-grid_step*grid_size);
				glVertex3f(0,grid_step*i,+grid_step*grid_size);

			}
			glEnd();

		}
		
		
		QMouseEvent *mouseEvent0; 
		
		void onMouseMoveRelease(QMouseEvent event0, QMouseEvent event1, bool apply){
				
			const float pi = 3.141592654;
	
			// screen center coordinates on press
			float x0 =  (event0.x()-0.5*width())  / height();
			float y0 = -(event0.y()-0.5*height()) / height();
	
			// screen center coordinates on move / release
			float x1 =  (event1.x()-0.5*width())  / height();
			float y1 = -(event1.y()-0.5*height()) / height();
	
			float dx = x1-x0;
			float dy = y1-y0;
	
			float a0 = atan2(y0,x0);
			float a1 = atan2(y1,x1);
	
			float da = a1-a0; 
	
			//int modifers = QApplication::keyboardModifiers();
			int buttons = event0.buttons();
			
			// Rotation XY
			if (buttons == Qt::LeftButton)
				eye.rotateXY(dx, dy, apply);
	
			// Rotation around Z
			if (buttons == Qt::RightButton)
				eye.rotateZ(da, apply);
			
		}
		
		void wheelEvent(QWheelEvent *event){
			
			if (event->buttons()==Qt::RightButton)
				pointSize+=-(event->delta()/120.0f)/scale_steps;
			
			if (event->buttons()==0)
				scale+=-(event->delta()/120.0f)/scale_steps;
		}
		
		void mousePressEvent(QMouseEvent *event){
			mouseEvent0 = new QMouseEvent(*event);
		}

		void mouseMoveEvent(QMouseEvent *event) {
	      onMouseMoveRelease(*mouseEvent0, *event, false);
		}

		void mouseReleaseEvent(QMouseEvent *event){
			
			if (event->buttons()!=0) return; //??
			onMouseMoveRelease(*mouseEvent0, *event, true);

			if (mouseEvent0) delete mouseEvent0; 
			
		}
		
		double w_a1, w_a2;
		
		virtual void keyPressEvent(QKeyEvent *event){
			
			if (event->key() == Qt::Key_F) 
				setWindowState(windowState()^Qt::WindowFullScreen);
			
			if (event->key() == Qt::Key_Space)
				timeFlag = !timeFlag;
			
			if (event->key() == Qt::Key_X) {
				eye.RM = eye.R = eye.R.module()*fVector(1,0,0);
				eye.UM = eye.U = fVector(0,0,1);
			}
			
			if (event->key() == Qt::Key_Y) {
				eye.RM = eye.R = -eye.R.module()*fVector(0,1,0);
				eye.UM = eye.U = fVector(0,0,1);
			}

			if (event->key() == Qt::Key_P) perspectiveF=!perspectiveF;
			if (event->key() == Qt::Key_G) gridF=!gridF;
			if (event->key() == Qt::Key_V) pivotF=!pivotF;
			if (event->key() == Qt::Key_QuoteLeft) infoF=!infoF; // ~
			if (event->key() == Qt::Key_D) depthF=!depthF; // ~
			if (event->key() == Qt::Key_C) centerF=!centerF;
			
			if (event->key() == Qt::Key_Z) {
				eye.RM = eye.R = eye.R.module()*fVector(0,0,1);
				eye.UM = eye.U = fVector(0,1,0);
			}				
			
			if (event->key() == Qt::Key_Escape)
				if (windowState()) setWindowState(windowState()^Qt::WindowFullScreen);
				else exit(0);
			
			if (event->key() == Qt::Key_Insert)	if (fov<179) fov++;
			if (event->key() == Qt::Key_Delete)	if (fov> 1) fov--;
			
			if (event->key() == Qt::Key_Insert)	if (fov<179) fov++;
			if (event->key() == Qt::Key_Delete)	if (fov> 1) fov--;
			
			if (event->key() == Qt::Key_W) centerx+=0.1;
			if (event->key() == Qt::Key_S) centerx-=0.1;			
			if (event->key() == Qt::Key_A) centery+=0.05;
			if (event->key() == Qt::Key_D) centery-=0.05;					
			
			//if (event->key() == Qt::Key_W) { eye.R.z+=0.1; eye.C.z+=0.1; };
			//if (event->key() == Qt::Key_S) { eye.R.z-=0.1; eye.C.z-=0.1; };
			
			
			
		}
		
		
		dVector V;
		void glPrint(fVector R, const char *string, void *font) {
			glRasterPos3fv(R);
			int len = (int) strlen(string);
			for (int i = 0; i < len; i++) 
				glutBitmapCharacter(font, string[i]);
		}	
		
		
		
		void glPrint(int x, int y, const char *string, void *font) {
	
			// save current projection matrix
			glMatrixMode(GL_PROJECTION); 
			glPushMatrix();
	
			// get viewport size
			GLint viewport[4];
			glGetIntegerv(GL_VIEWPORT, viewport );
	
			// set projection matrix to ortho settings
			glLoadIdentity();
			glOrtho(0, viewport[2], viewport[3], 0, -1, 1);
	
			// save current modelview matrix
			glMatrixMode(GL_MODELVIEW); 
			glPushMatrix();
	
			//text drowing
			glLoadIdentity();

			int indent = 15; //
			glRasterPos2i(x, y);
			int len = (int) strlen(string);
			for (int i = 0; i < len; i++) {
		
				if (string[i] == '\n') {
					y+=indent;
					glRasterPos2i(x, y);
					continue;
				}
				glutBitmapCharacter(font, string[i]);
		
			}
	
			// restore previos settings
			glMatrixMode(GL_PROJECTION); 
			glPopMatrix();
	
			glMatrixMode(GL_MODELVIEW); 
			glPopMatrix();
			
		}

		
};
























